<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Analyser.getByteTimeDomainData()
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let sampleRate = 48000;
      // The size of the analyser frame.  Anything larger than 128 is ok, but
      // should be long enough to capture the peaks of the oscillator waveform.
      let fftSize = 256;
      // Number of frames to render.  Should be greater than the fftSize, but is
      // otherwise arbitrary.
      let renderFrames = 2 * fftSize;

      let audit = Audit.createTaskRunner();

      // Test that getByteTimeDomainData returns the correct values.  This test
      // depends on getFloatTimeDomainData returning the correct data (for which
      // there is already a test).
      audit.define('byte-data', (task, should) => {
        let context = new OfflineAudioContext(1, renderFrames, sampleRate);

        // Create a sawtooth as the signal under test.  A sine wave or triangle
        // wave would probably also work.
        let src = context.createOscillator();
        src.type = 'sawtooth';
        // Choose a frequency high enough that we get at least a full period in
        // one analyser fftSize frame.  Otherwise, the frequency is arbitrary.
        src.frequency.value = 440;

        // Gain node to make sure the signal goes somewhat above 1, for testing
        // clipping.
        let gain = context.createGain();
        gain.gain.value = 1.5;

        // The analyser node to test
        let analyser = context.createAnalyser();
        analyser.fftSize = fftSize;

        // Connect the graph.
        src.connect(gain);
        gain.connect(analyser);
        analyser.connect(context.destination);

        // Stop rendering after one analyser frame so we can grab the data.
        context.suspend(fftSize / sampleRate)
            .then(function() {
              let floatData = new Float32Array(fftSize);
              let byteData = new Uint8Array(fftSize);

              analyser.getFloatTimeDomainData(floatData);
              analyser.getByteTimeDomainData(byteData);

              // Use the float data to compute the expected value for the byte
              // data.
              let expected = new Float32Array(fftSize);
              for (let k = 0; k < fftSize; ++k) {
                // It's important to do Math.fround to match the
                // single-precision float in the implementation!
                let value = Math.fround(128 * Math.fround(1 + floatData[k]));
                // Clip the result to lie in the range [0, 255].
                expected[k] = Math.floor(Math.min(255, Math.max(0, value)));
              }

              // Find the first index of the first sample that exceeds +1 or -1.
              // The test MUST have at least one such value.
              let indexMax = floatData.findIndex(function(x) {
                return x > 1;
              });
              let indexMin = floatData.findIndex(function(x) {
                return x < -1;
              });

              should(indexMax, 'Index of first sample greater than +1')
                  .beGreaterThanOrEqualTo(0);
              should(indexMin, 'Index of first sample less than -1')
                  .beGreaterThanOrEqualTo(0);

              // Verify explicitly that clipping happened correctly at the above
              // indices.
              should(
                  byteData[indexMax],
                  'Clip  ' + floatData[indexMax].toPrecision(6) +
                      ': byteData[' + indexMax + ']')
                  .beEqualTo(255);
              should(
                  byteData[indexMin],
                  'Clip ' + floatData[indexMin].toPrecision(6) + ': byteData[' +
                      indexMin + ']')
                  .beEqualTo(0);

              // Verify that all other samples are computed correctly.
              should(byteData, 'Byte data').beEqualToArray(expected);
            })
            .then(context.resume.bind(context))

        src.start();
        context.startRendering().then(() => task.done());
      });

      audit.run();
    </script>
  </body>
</html>
